package it.iaproject.ai;

import it.iaproject.Cell;
import it.iaproject.Map;

import java.util.LinkedList;
import java.util.Vector;

public class MarioIA extends Thread {

	private Cell myCell;
	private Map map;
	//private Cell firstCellNearTheRiver;

	public LinkedList<Cell> visited;
	private int heuristic = 0;

	// Campi utili per la terza euristica
	private int deep = 4;
	private int counter = 0;

	// Conto il numero di volte che incontro un lato della mappa
	// Se lo incontro due volte significa che ho gia' perlustrato tutta la riva
	// del fiume senza trovare un ponti => FALLIMENTO
	private int fail = 2;


	private boolean stop = false;
	private boolean paused = false;
	private boolean nearTheRiver = false;
	private boolean crossTheRiver =  false;
	private int numOfNodes = 0;

	public static int frameRate = 1000;

	public MarioIA(Cell myCell, Map map) {
		visited = new LinkedList<Cell>();
		this.myCell = myCell;
		this.map = map;
	}

	public void run () {

		if (heuristic == 0)
			System.out.println("Prima euristica, ricerca in profondita' in base alla distanza da Peach");
		else if (heuristic == 1)
			System.out.println("Seconda euristica, ricerca in profondita' in base alla dimensione della porzione di mappa");
		else if (heuristic == 2)
			System.out.println("Terza euristica, ricerca in \"ampiezza\"");


		while(!isStop()) {
			try {
				if (fail == 0) {
					System.out.println("L'euristica #" + (heuristic+1) + " non ha trovato una soluzione al problema");
				}

				// Trovo una soluzione o meno => ripristino e passo alla prossima euristica
				if (myCell.getStatus() == Cell.WIN || fail == 0) {

					Thread.sleep(frameRate);

					map.restoreOriginalMap();
					myCell = map.getMarioCell();
					map.saveOnCSV(heuristic, numOfNodes);
					heuristic++;

					// Restore IA fields
					fail = 2;
					numOfNodes = 0;
					visited = new LinkedList<Cell>();
					counter = 0;
					nearTheRiver = false;
					crossTheRiver =  false;


					if (heuristic == 0)
						System.out.println("Prima euristica, ricerca in profondita' in base alla distanza da Peach");
					else if (heuristic == 1)
						System.out.println("Seconda euristica, ricerca in profondita' in base alla dimensione della porzione di mappa");
					else if (heuristic == 2)
						System.out.println("Terza euristica, ricerca in \"ampiezza\"");
				}


				if(heuristic > 2) {
					setStop(true);
				} else {

					if(!nearTheRiver)
						greenHeuristic();
					else {
						if (heuristic == 0) {
							riverHeuristicOne();
						}
						else if (heuristic == 1) {
							riverHeuristicTwo();
						}
						else {
							if(visited.size() == 0) {
								visited.add(myCell);
							}
							riverHeuristicThree();
						}
					}
					if (isPaused()) {
						synchronized (this) {	
							map.ia.wait();
							map.ia.setPaused(false);
						}
					}
					Thread.sleep(frameRate);
				}
			} catch (InterruptedException e) {}
		}
	}

	private void greenHeuristic () {
		Vector<Cell> neighboursCells = map.neighbour8Cells(myCell);
		Cell currentCell;
		Cell bestCell = null;
		double min = Integer.MAX_VALUE;
		//System.out.println("myCell ["+ myCell.getRow()+"]["+myCell.getCol()+"]");
		for(int i = 0; i < neighboursCells.size() && !nearTheRiver; i++) {
			currentCell = neighboursCells.elementAt(i);
			if(currentCell.getStatus()==Cell.FREE || currentCell.getStatus()==Cell.PEACH) {
				currentCell.setDistance(currentCell.getDistance()==-1?calcDistance(currentCell):currentCell.getDistance());
				//System.out.println("currentCell.getDistance() " + currentCell.getDistance() + " ["+ currentCell.getRow()+"]["+currentCell.getCol()+"] status : " + currentCell.getStatus());
				if(currentCell.getDistance() < min) {
					min = currentCell.getDistance();
					bestCell = currentCell;
					//System.out.println("min " + min + " ["+ bestCell.getRow()+"]["+bestCell.getCol()+"]");
				}
			} else {
				if(((currentCell.getStatus()==Cell.RIVER || currentCell.getStatus()==Cell.BRIDGE)) && !crossTheRiver) {
					nearTheRiver = true;
				}
			}
		}

		map.ui.updateMapPanel(myCell);
		try {
			Thread.sleep(frameRate);
		} catch (InterruptedException e) {}


		if(nearTheRiver != true) {
			//System.out.println("FINAL min " + min + " ["+ bestCell.getRow()+"]["+bestCell.getCol()+"]");
			if(bestCell.getStatus() != Cell.PEACH)
				bestCell.setStatus(Cell.MARIO);
			else {
				System.out.println("WIN!!!!!");
				bestCell.setStatus(Cell.WIN);
			}

			if(myCell.getStatus() == Cell.MARIO)
				myCell.setStatus(Cell.FREE);
			if(myCell.getStatus() == Cell.MARIOONTHEBRIDGE)
				myCell.setStatus(Cell.BRIDGE);
			myCell = bestCell;
			incrNumOfNodes();
			map.ui.updateMapPanel(myCell);
		}
	}
	
	private void riverHeuristicOne() {
		Vector<Cell> neighboursCells = map.neighbour8Cells(myCell);
		//visited.add(myCell);

		Vector<Cell> yesRiver = new Vector<Cell>();
		Vector<Cell> noRiver = new Vector<Cell>();
		Vector<Cell> nearTheRiverCells = new Vector<Cell>();

		boolean foundBridge = false;

		for(int i=0; i<neighboursCells.size() && !foundBridge; i++) {
			if(neighboursCells.elementAt(i).getStatus() == Cell.BRIDGE) {
				foundBridge = true;
				nearTheRiverCells.add(neighboursCells.elementAt(i));
			} else {
				if(neighboursCells.elementAt(i).getStatus() == Cell.RIVER) {
					yesRiver.add(neighboursCells.elementAt(i));
				} else {
					noRiver.add(neighboursCells.elementAt(i));
				}
			}
		}

		for(int i=0; i<noRiver.size() && !foundBridge; i++) {
			boolean near = false;
			for(int j=0; j<yesRiver.size()  && !near;j++) {
				if(isNear(noRiver.elementAt(i), yesRiver.elementAt(j)) && visited.indexOf(noRiver.elementAt(i)) == -1) {
					near = true;
					nearTheRiverCells.add(noRiver.elementAt(i));
				}
			}
		}

		Cell bestCell = null;
		double min = Integer.MAX_VALUE;
		for(int i=0; i< nearTheRiverCells.size();i++){
			if(!nearTheRiverCells.elementAt(i).isTaboo()) {
				Cell currentCell;
				currentCell = nearTheRiverCells.elementAt(i);
				currentCell.setDistance(currentCell.getDistance()==-1?calcDistance(currentCell):currentCell.getDistance());
				if(currentCell.getDistance() < min) {
					min = currentCell.getDistance();
					bestCell = currentCell;
				}
			}
		}

		if(bestCell != null) {
			visited.add(myCell);
			if(bestCell.getStatus() == Cell.FREE) {
				bestCell.setStatus(Cell.MARIO);
				myCell.setStatus(Cell.FREE);
				myCell = bestCell;
				incrNumOfNodes();
			}
			if(bestCell.getStatus() == Cell.BRIDGE) {
				crossTheRiver(bestCell);
			}
		} else {
			System.out.println("Backtracking!!!");
			fail--;
			if(fail == 0) {
				System.out.println("Istanza senza soluzione");
			} else {
				for(int i = visited.size()-1; i >= 0 ; i--) {
					myCell.setStatus(Cell.FREE);
					if(!(myCell.getCol() == visited.get(i).getCol() || myCell.getRow() == visited.get(i).getRow())) {
						if(map.getCell(myCell.getRow(), visited.get(i).getCol()).getStatus() != Cell.RIVER) {
							map.getCell(myCell.getRow(), visited.get(i).getCol()).setTaboo(true);
						} else if(map.getCell(visited.get(i).getRow(), myCell.getCol()).getStatus() != Cell.RIVER) {
							map.getCell(visited.get(i).getRow(), myCell.getCol()).setTaboo(true);
						} else {
							System.out.println("TABOO ERROR");
						}
					}
					myCell = visited.get(i);
					myCell.setStatus(Cell.MARIO);
					incrNumOfNodes();
					map.ui.updateMapPanel(myCell);
					try {
						Thread.sleep(frameRate/2);
					} catch (InterruptedException e) { e.printStackTrace();	}
				}
			}
		}
		map.ui.updateMapPanel(myCell);

	}

	private void riverHeuristicTwo() {
		Vector<Cell> neighboursCells = map.neighbour8Cells(myCell);
		//visited.add(myCell);

		Vector<Cell> yesRiver = new Vector<Cell>();
		Vector<Cell> noRiver = new Vector<Cell>();
		Vector<Cell> nearTheRiverCells = new Vector<Cell>();

		boolean foundBridge = false;

		for(int i=0; i<neighboursCells.size() && !foundBridge; i++) {
			if(neighboursCells.elementAt(i).getStatus() == Cell.BRIDGE) {
				foundBridge = true;
				nearTheRiverCells.add(neighboursCells.elementAt(i));
			} else {
				if(neighboursCells.elementAt(i).getStatus() == Cell.RIVER) {
					yesRiver.add(neighboursCells.elementAt(i));
				} else {
					noRiver.add(neighboursCells.elementAt(i));
				}
			}
		}

		for(int i=0; i<noRiver.size() && !foundBridge; i++) {
			boolean near = false;
			for(int j=0; j<yesRiver.size()  && !near;j++) {
				if(isNear(noRiver.elementAt(i), yesRiver.elementAt(j)) && visited.indexOf(noRiver.elementAt(i)) == -1) {
					near = true;
					nearTheRiverCells.add(noRiver.elementAt(i));
				}
			}
		}

		//Devo scegliere la cella in modo da cominciare a controllare la riva nella porzione di mappa statisticamente piu' piccola
		//Quindi scelgo la cella piu' vicina ai lati
		Cell bestCell = null;
		double min = Integer.MAX_VALUE;
		for(int i=0; i< nearTheRiverCells.size();i++){
			if(!nearTheRiverCells.elementAt(i).isTaboo()) {
				Cell currentCell = nearTheRiverCells.elementAt(i);
				currentCell.setDistanceFromSides(currentCell.getDistanceFromSides()==-1?calcDistanceFromSides(currentCell):currentCell.getDistanceFromSides());
				if(currentCell.getDistanceFromSides() < min) {
					min = currentCell.getDistanceFromSides();
					bestCell = currentCell;
				}
			}
		}

		if(bestCell != null){
			visited.add(myCell);
			if(bestCell.getStatus() == Cell.FREE) {
				bestCell.setStatus(Cell.MARIO);
				myCell.setStatus(Cell.FREE);
				myCell = bestCell;
				incrNumOfNodes();
			}
			if(bestCell.getStatus() == Cell.BRIDGE) {
				crossTheRiver(bestCell);
			}
		} else {
			System.out.println("Backtracking!!!");
			fail--;
			if(fail == 0) {
				System.out.println("Istanza senza soluzione");
			} else {
				for(int i = visited.size()-1; i >= 0 ; i--) {
					myCell.setStatus(Cell.FREE);
					if(!(myCell.getCol() == visited.get(i).getCol() || myCell.getRow() == visited.get(i).getRow())) {
						if(map.getCell(myCell.getRow(), visited.get(i).getCol()).getStatus() != Cell.RIVER) {
							map.getCell(myCell.getRow(), visited.get(i).getCol()).setTaboo(true);
						} else if(map.getCell(visited.get(i).getRow(), myCell.getCol()).getStatus() != Cell.RIVER) {
							map.getCell(visited.get(i).getRow(), myCell.getCol()).setTaboo(true);
						} else {
							System.out.println("TABOO ERROR");
						}
					}
					myCell = visited.get(i);
					myCell.setStatus(Cell.MARIO);
					incrNumOfNodes();
					map.ui.updateMapPanel(myCell);
					try {
						Thread.sleep(frameRate/2);
					} catch (InterruptedException e) { e.printStackTrace();	}
				}
			}
		}
		map.ui.updateMapPanel(myCell);
	}
	
	private void riverHeuristicThree() {
		counter++;
		if(counter % deep == 0) {
			if(myCell == visited.getFirst()) {
				for(int i = 1; i < visited.size(); i++) {
					myCell.setStatus(Cell.FREE);
					if(!(myCell.getCol() == visited.get(i).getCol() || myCell.getRow() == visited.get(i).getRow())) {
						if(map.getCell(myCell.getRow(), visited.get(i).getCol()).getStatus() != Cell.RIVER) {
							map.getCell(myCell.getRow(), visited.get(i).getCol()).setTaboo(true);
						} else if(map.getCell(visited.get(i).getRow(), myCell.getCol()).getStatus() != Cell.RIVER) {
							map.getCell(visited.get(i).getRow(), myCell.getCol()).setTaboo(true);
						} else {
							System.out.println("TABOO ERROR");
						}
					}
					myCell = visited.get(i);
					myCell.setStatus(Cell.MARIO);
					incrNumOfNodes();
					map.ui.updateMapPanel(myCell);
					try {
						Thread.sleep(frameRate/2);
					} catch (InterruptedException e) { e.printStackTrace();	}
				}
			} else if(myCell == visited.getLast()) {
				for(int i = visited.size()-2; i >= 0; i--) {
					myCell.setStatus(Cell.FREE);
					if(!(myCell.getCol() == visited.get(i).getCol() || myCell.getRow() == visited.get(i).getRow())) {
						if(map.getCell(myCell.getRow(), visited.get(i).getCol()).getStatus() != Cell.RIVER) {
							map.getCell(myCell.getRow(), visited.get(i).getCol()).setTaboo(true);
						} else if(map.getCell(visited.get(i).getRow(), myCell.getCol()).getStatus() != Cell.RIVER) {
							map.getCell(visited.get(i).getRow(), myCell.getCol()).setTaboo(true);
						} else {
							System.out.println("TABOO ERROR");
						}
					}
					myCell = visited.get(i);
					myCell.setStatus(Cell.MARIO);
					incrNumOfNodes();
					map.ui.updateMapPanel(myCell);
					try {
						Thread.sleep(frameRate/2);
					} catch (InterruptedException e) { e.printStackTrace();	}
				}
			}
		} else {
			Vector<Cell> neighboursCells = map.neighbour8Cells(myCell);

			Vector<Cell> yesRiver = new Vector<Cell>();
			Vector<Cell> noRiver = new Vector<Cell>();
			Vector<Cell> nearTheRiverCells = new Vector<Cell>();

			boolean foundBridge = false;

			for(int i=0; i<neighboursCells.size() && !foundBridge; i++) {
				if(neighboursCells.elementAt(i).getStatus() == Cell.BRIDGE) {
					foundBridge = true;
					nearTheRiverCells.add(neighboursCells.elementAt(i));
				} else {
					if(neighboursCells.elementAt(i).getStatus() == Cell.RIVER) {
						yesRiver.add(neighboursCells.elementAt(i));
					} else {
						noRiver.add(neighboursCells.elementAt(i));
					}
				}
			}

			for(int i=0; i<noRiver.size() && !foundBridge; i++) {
				boolean near = false;
				for(int j=0; j<yesRiver.size()  && !near;j++) {
					if(isNear(noRiver.elementAt(i), yesRiver.elementAt(j)) && visited.indexOf(noRiver.elementAt(i)) == -1) {
						near = true;
						nearTheRiverCells.add(noRiver.elementAt(i));
					}
				}
			}

			//Devo scegliere la cella in modo da cominciare a controllare la riva nella porzione di mappa statisticamente piu' piccola
			//Quindi scelgo la cella piu' vicina ai lati
			Cell bestCell = null;
			double min = Integer.MAX_VALUE;
			for(int i=0; i< nearTheRiverCells.size();i++){
				if(!nearTheRiverCells.elementAt(i).isTaboo()) {
					Cell currentCell = nearTheRiverCells.elementAt(i);
					currentCell.setDistanceFromSides(currentCell.getDistanceFromSides()==-1?calcDistanceFromSides(currentCell):currentCell.getDistanceFromSides());
					if(currentCell.getDistanceFromSides() < min) {
						min = currentCell.getDistanceFromSides();
						bestCell = currentCell;
					}
				}
			}

			if(bestCell != null){
				if(bestCell.getStatus() == Cell.FREE) {
					bestCell.setStatus(Cell.MARIO);
					myCell.setStatus(Cell.FREE);
					if(myCell == visited.getFirst()) {
						visited.addFirst(bestCell);
					} else if(myCell == visited.getLast()) {
						visited.addLast(bestCell);
					}
					myCell = bestCell;
					incrNumOfNodes();
				}
				if(bestCell.getStatus() == Cell.BRIDGE) {
					crossTheRiver(bestCell);
				}
			} else {
				System.out.println("Backtracking!!!");
				fail--;
				if(fail == 0) {
					System.out.println("Istanza senza soluzione");
				} else {
					if(myCell == visited.getFirst()) {
						for(int i = 1; i < visited.size(); i++) {
							myCell.setStatus(Cell.FREE);
							if(!(myCell.getCol() == visited.get(i).getCol() || myCell.getRow() == visited.get(i).getRow())) {
								if(map.getCell(myCell.getRow(), visited.get(i).getCol()).getStatus() != Cell.RIVER) {
									map.getCell(myCell.getRow(), visited.get(i).getCol()).setTaboo(true);
								} else if(map.getCell(visited.get(i).getRow(), myCell.getCol()).getStatus() != Cell.RIVER) {
									map.getCell(visited.get(i).getRow(), myCell.getCol()).setTaboo(true);
								} else {
									System.out.println("TABOO ERROR");
								}
							}
							myCell = visited.get(i);
							myCell.setStatus(Cell.MARIO);
							incrNumOfNodes();
							map.ui.updateMapPanel(myCell);
							try {
								Thread.sleep(frameRate/2);
							} catch (InterruptedException e) { e.printStackTrace();	}
						}
					} else if(myCell == visited.getLast()) {
						for(int i = visited.size()-2; i >= 0; i--) {
							myCell.setStatus(Cell.FREE);
							if(!(myCell.getCol() == visited.get(i).getCol() || myCell.getRow() == visited.get(i).getRow())) {
								if(map.getCell(myCell.getRow(), visited.get(i).getCol()).getStatus() != Cell.RIVER) {
									map.getCell(myCell.getRow(), visited.get(i).getCol()).setTaboo(true);
								} else if(map.getCell(visited.get(i).getRow(), myCell.getCol()).getStatus() != Cell.RIVER) {
									map.getCell(visited.get(i).getRow(), myCell.getCol()).setTaboo(true);
								} else {
									System.out.println("TABOO ERROR");
								}
							}
							myCell = visited.get(i);
							myCell.setStatus(Cell.MARIO);
							incrNumOfNodes();
							map.ui.updateMapPanel(myCell);
							try {
								Thread.sleep(frameRate/2);
							} catch (InterruptedException e) { e.printStackTrace();	}
						}
					}
					deep = Integer.MAX_VALUE;
				}
			}
			map.ui.updateMapPanel(myCell);
		}
	}

	private Cell getCell(int row, int col) {
		if(row < 0 || row == map.getDim() || col < 0 || col == map.getDim()) {
			Cell c = new Cell(-1, -1);
			c.setStatus(Cell.RIVER);
			return c;
		} else {
			return map.getCell(row, col);
		}
	}

	private void crossTheRiver(Cell bestCell) {
		bestCell.setStatus(Cell.MARIOONTHEBRIDGE);

		int myRow = myCell.getRow();
		int myCol = myCell.getCol();

		int bridgeRow = bestCell.getRow();
		int bridgeCol = bestCell.getCol();

		Cell cellTarget = null;

		// orizzontale
		if(bridgeRow == myRow) {
			if(bridgeCol < myCol) {
				// da dx a sx
				cellTarget = getCell(myRow, bridgeCol-1);
			} else {
				// da sx a dx
				cellTarget = getCell(myRow, bridgeCol+1);
			}
		} else
			//verticale
			if(bridgeCol == myCol) {
				if(bridgeRow < myRow) {
					// da down a up
					cellTarget = getCell(bridgeRow-1, myCol);
				} else {
					// da up a down
					cellTarget = getCell(bridgeRow+1, myCol);
				}
			} else
				if(bridgeCol < myCol && bridgeRow < myRow) {
					// da down dx to up sx
					cellTarget = getCell(bridgeRow-1, bridgeCol);
					if(cellTarget.getStatus() == Cell.RIVER)
						cellTarget = getCell(bridgeRow, bridgeCol-1);
				} else 
					if(bridgeCol > myCol && bridgeRow > myRow) {
						// da up sx to down dx 
						cellTarget = getCell(bridgeRow, bridgeCol+1);
						if(cellTarget.getStatus() == Cell.RIVER)
							cellTarget = getCell(bridgeRow+1, bridgeCol);
					} else 
						if(bridgeCol > myCol && bridgeRow < myRow) {
							//da down sx to up dx
							cellTarget = getCell(bridgeRow-1, bridgeCol);
							if(cellTarget.getStatus() == Cell.RIVER)
								cellTarget = getCell(bridgeRow, bridgeCol+1);
						} else 
							if(bridgeCol < myCol && bridgeRow > myRow) { 
								// da up dx to down sx
								cellTarget = getCell(bridgeRow+1, bridgeCol);
								if(cellTarget.getStatus() == Cell.RIVER)
									cellTarget = getCell(bridgeRow, bridgeCol-1);
							} else {
								System.out.println("Houston abbiamo un problema!!");
							}

		crossTheRiver = true;
		nearTheRiver = false;

		myCell.setStatus(Cell.FREE);
		myCell = bestCell;
		incrNumOfNodes();

		map.ui.updateMapPanel(myCell);

		try {
			Thread.sleep(frameRate);
		} catch (InterruptedException e) { e.printStackTrace();	}

		// Forzo il passaggio del ponte per poi chiuderlo

		myCell.setStatus(Cell.BRIDGE);
		myCell = cellTarget;
		if(myCell.getStatus() == Cell.PEACH)
			myCell.setStatus(Cell.WIN);
		else
			myCell.setStatus(Cell.MARIO);
		incrNumOfNodes();			

	}

	private boolean isNear(Cell field, Cell water) {
		if((Math.abs(field.getRow()-water.getRow()) <= 1) && (Math.abs(field.getCol()-water.getCol()) <= 1)) {
			return true;
		}
		return false;
	}

	private double calcDistance (Cell cell) {
		// Distanza euclidea
		return (((double)(int)(Math.sqrt(Math.pow(cell.getCol()-map.getPeachCell().getCol(),2) + Math.pow(cell.getRow()-map.getPeachCell().getRow(),2))*100))/100);

		// Distanza di Manhattan
		// return (double)(Math.abs(cell.getCol()-map.getPeachCell().getCol()) + Math.abs(cell.getRow()-map.getPeachCell().getRow()));
	}

	private double calcDistanceFromSides (Cell cell) {
		return (((double)((
				map.getDim() - cell.getCol() < map.getDim()/2 ? map.getDim() - cell.getCol() : cell.getCol()    
				)+(
						map.getDim() - cell.getRow() < map.getDim()/2 ? map.getDim() - cell.getRow() : cell.getRow() 
						))*100)/100);
	}

	public boolean isStop() {
		return stop;
	}

	public void setStop(boolean stop) {
		this.stop = stop;
	}

	public synchronized boolean isPaused() {
		return paused;
	}

	public synchronized  void setPaused(boolean paused) {
		this.paused = paused;
	}

	public void incrNumOfNodes() {
		this.numOfNodes++;
	}

	public int getNumOfNodes() {
		return numOfNodes;
	}

	public void setNumOfNodes(int numOfNodes) {
		this.numOfNodes = numOfNodes;
	}

	public int getHeuristic() {
		return heuristic;
	}
}